相互 TLS 認証で API Gateway のバックエンドの S3 にアクセスできるかやってみた
こんにちは、アノテーション テクニカルサポートチームの中野です。
API Gateway のバックエンドに S3 を設定するパターンと API Gateway の通信で相互 TLS 認証(mTLS)を行うパターンの組み合わせを試す機会があったので、以下に手順をまとめてみました。
構成
以下のような構成で構築してみます。
また、mTLS の詳しい仕組みについては、以下がわかりやすかったので参照ください。
やってみた
前提条件
構築するまえに、前提として API Gateway のカスタムドメインを作成するために、以下を準備しておきます。
- Route53 へのドメインの登録
- ACM で証明書発行
準備が完了したら、以下手順を進めてきます。
ステップ 1: S3 に静的ファイルをアップロード
まず、S3 を作成します。
何らかの適当な文字を描画する HTML ファイルをアップロードしておきます。
また、API Gateway から今回作った S3 のみにアクセスできるように、以下のような IAM ポリシーを作成しておきます。
{ "Version": "2012-10-17", "Statement": [ { "Action": [ "s3:Get*", "s3:List*" ], "Resource": [ "arn:aws:s3:::<S3 バケット名>", "arn:aws:s3:::<S3 バケット名>/*" ], "Effect": "Allow" } ] }
このポリシーで、API Gateway が信頼されたエンティティ(ロールをアタッチできる対象)となるように IAM ロールを作成します。
IAM ロールは、ステップ 2 の API Gateway を構築する際に利用します。
ステップ 2: API Gateway の構築
では、S3 へプロキシする API Gateway を作成します。
API のタイプは、REST API で作成していきます。
リソースを新たに作成します。 ここでは、/page というパスで作成しました。
リソースに対して GET メソッドを追加して、以下のような設定を行います。
注意点として、「パスの上書きの使用」の部分に {S3 バケット名}/{取得するファイル名} となるように記載します。
また、実行ロール部分は、ステップ 1 で作成した API Gateway から S3 へのアクセスの許可を行うための IAM ロールの ARN を記載します。
作成が完了したら、適切なステージ名でデプロイします。
なお、ここで API Gateway のデフォルトのエンドポイントを無効化しておきます。
デフォルトのエンドポイントを無効化していない状態で、相互 TLS 認証を API Gateway へ設定すると、クライアント証明書なしでのアクセスができてしまいますので、運用前に見落としがないように注意してください。
再度、設定を反映するために、再度ステージへデプロイしておきます。
ステップ 3: カスタムドメインの作成
カスタムドメインを作成します。
このとき、一旦相互 TLS 認証を無効にしたまま、ドメイン作成を実行します。
ステップ 4: クライアント証明書の発行
クライアント証明書を自分のローカルマシンで作成します。
以下のブログの手順で実行しました。
ルート CA の秘密鍵を作成します。
$ openssl genrsa -out RootCA.key 4096 Generating RSA private key, 4096 bit long modulus ......................................................................................................................................................++ ..................................++ e is 65537 (0x10001)
続いて、自己署名 CA 証明書作成します。
$ openssl req -new -x509 -days 36500 -key RootCA.key -out RootCA.pem You are about to be asked to enter information that will be incorporated into your certificate request. What you are about to enter is what is called a Distinguished Name or a DN. There are quite a few fields but you can leave some blank For some fields there will be a default value, If you enter '.', the field will be left blank. ----- Country Name (2 letter code) []:JP State or Province Name (full name) []:FUKUOKA Locality Name (eg, city) []: Organization Name (eg, company) []:Annotation Organizational Unit Name (eg, section) []:Technical Support Common Name (eg, fully qualified host name) []:an-nakano ca Email Address []:
クライアント側の秘密鍵を作成します。
$ openssl genrsa -out my_client.key 2048 Generating RSA private key, 2048 bit long modulus .............................................................................................................................+++ .....................................+++ e is 65537 (0x10001)
秘密鍵をもとに CSR を作成します。
$ openssl req -new -key my_client.key -out my_client.csr You are about to be asked to enter information that will be incorporated into your certificate request. What you are about to enter is what is called a Distinguished Name or a DN. There are quite a few fields but you can leave some blank For some fields there will be a default value, If you enter '.', the field will be left blank. ----- Country Name (2 letter code) []:JP State or Province Name (full name) []:FUKUOKA Locality Name (eg, city) []: Organization Name (eg, company) []:Annotation Organizational Unit Name (eg, section) []:Technical Support Common Name (eg, fully qualified host name) []:an-nakano client Email Address []: Please enter the following 'extra' attributes to be sent with your certificate request A challenge password []:
最後にクライント証明書を作成します。
$ openssl x509 -req -in my_client.csr -CA RootCA.pem -CAkey RootCA.key -set_serial 01 -out my_client.pem -days 36500 -sha256 Signature ok subject=/C=JP/ST=FUKUOKA/O=Annotation/OU=Technical Support/CN=an-nakano client Getting CA Private Key
作成した CA 証明書を適当な S3 バケットに保存します。
$ aws s3 cp RootCA.pem s3://<CA証明書用S3バケット名>/ upload: ./RootCA.pem to s3://<CA証明書用S3バケット名>/RootCA.pem
ステップ 5: API Gateway で相互 TLS 認証を設定
では、ステップ 3 で作成したカスタムドメインに、ステップ 4 で作成した S3 にアップロード済みの CA 証明書を設定します。
カスタムドメインのステータスが「利用可能」になるまで待ちます。
最後に、カスタムドメインの API マッピング設定で、API Gateway のステージを関連付けます。
ここで、Route53 のエイリアスレコードで API Gateway へトラフィック転送するように設定して完了です。
ステップ 6: 検証
では、クライアント証明書ありで、curl でアクセスしてみます。
$ curl -v -i --key my_client.key --cert my_client.pem https://api.example.com/page * Trying 54.249.165.209... * TCP_NODELAY set * Connected to api.example.com (54.249.165.209) port 443 (#0) * ALPN, offering h2 * ALPN, offering http/1.1 * successfully set certificate verify locations: * CAfile: /etc/ssl/cert.pem CApath: none * TLSv1.2 (OUT), TLS handshake, Client hello (1): * TLSv1.2 (IN), TLS handshake, Server hello (2): * TLSv1.2 (IN), TLS handshake, Certificate (11): * TLSv1.2 (IN), TLS handshake, Server key exchange (12): * TLSv1.2 (IN), TLS handshake, Request CERT (13): * TLSv1.2 (IN), TLS handshake, Server finished (14): * TLSv1.2 (OUT), TLS handshake, Certificate (11): * TLSv1.2 (OUT), TLS handshake, Client key exchange (16): * TLSv1.2 (OUT), TLS handshake, CERT verify (15): * TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1): * TLSv1.2 (OUT), TLS handshake, Finished (20): * TLSv1.2 (IN), TLS change cipher, Change cipher spec (1): * TLSv1.2 (IN), TLS handshake, Finished (20): * SSL connection using TLSv1.2 / ECDHE-RSA-AES128-GCM-SHA256 * ALPN, server accepted to use h2 * Server certificate: * subject: CN=example.com * start date: Nov 15 00:00:00 2021 GMT * expire date: Dec 14 23:59:59 2022 GMT * subjectAltName: host "api.example.com" matched cert's "*.example.com" * issuer: C=US; O=Amazon; OU=Server CA 1B; CN=Amazon * SSL certificate verify ok. * Using HTTP2, server supports multi-use * Connection state changed (HTTP/2 confirmed) * Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0 * Using Stream ID: 1 (easy handle 0x7ffd2f008200) > GET /page HTTP/2 > Host: api.example.com > User-Agent: curl/7.64.1 > Accept: */* > * Connection state changed (MAX_CONCURRENT_STREAMS == 128)! < HTTP/2 200 HTTP/2 200 < x-amzn-requestid: a0469407-1a6b-4472-xxxxxxxxxx x-amzn-requestid: a0469407-1a6b-4472-xxxxxxxxxx < x-amz-apigw-id: M_xxxxxxxxxx x-amz-apigw-id: M_xxxxxxxxxx < x-amzn-trace-id: Root=1-61fc83ab-xxxxxxxxxx x-amzn-trace-id: Root=1-61fc83ab-xxxxxxxxxx < content-type: application/json content-type: application/json < content-length: 38 content-length: 38 < date: Fri, 04 Feb 2022 01:38:51 GMT date: Fri, 04 Feb 2022 01:38:51 GMT < <html> Hello World!! Yeah!! </html> * Connection #0 to host api.example.com left intact * Closing connection 0
無事、アクセスできることを確認しました。
次に、クライアント証明書なしで、curl でアクセスしてみます。
$ curl -v -i https://api.example.com/page * Trying 18.180.7.23... * TCP_NODELAY set * Connected to api.example.com (18.180.7.23) port 443 (#0) * ALPN, offering h2 * ALPN, offering http/1.1 * successfully set certificate verify locations: * CAfile: /etc/ssl/cert.pem CApath: none * TLSv1.2 (OUT), TLS handshake, Client hello (1): * TLSv1.2 (IN), TLS handshake, Server hello (2): * TLSv1.2 (IN), TLS handshake, Certificate (11): * TLSv1.2 (IN), TLS handshake, Server key exchange (12): * TLSv1.2 (IN), TLS handshake, Request CERT (13): * TLSv1.2 (IN), TLS handshake, Server finished (14): * TLSv1.2 (OUT), TLS handshake, Certificate (11): * TLSv1.2 (OUT), TLS handshake, Client key exchange (16): * TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1): * TLSv1.2 (OUT), TLS handshake, Finished (20): * LibreSSL SSL_connect: SSL_ERROR_SYSCALL in connection to api.example.com:443 * Closing connection 0 curl: (35) LibreSSL SSL_connect: SSL_ERROR_SYSCALL in connection to api.example.com:443
アクセスされないことが確認できました。
最後に
API Gateway で S3 へ簡単にプロキシできる環境を構築できて、さらに API Gateway の認証として相互 TLS 認証を利用する例をまとめてみました。
クライアント証明書をもつユーザのみにサイトを公開するようなパターンに有用なのではないでしょうか。
アノテーション株式会社について
アノテーション株式会社は、クラスメソッド社のグループ企業として「オペレーション・エクセレンス」を担える企業を目指してチャレンジを続けています。「らしく働く、らしく生きる」のスローガンを掲げ、様々な背景をもつ多様なメンバーが自由度の高い働き方を通してお客様へサービスを提供し続けてきました。現在当社では一緒に会社を盛り上げていただけるメンバーを募集中です。少しでもご興味あれば、アノテーション株式会社 WEB サイトをご覧ください。